/*
 * TextDocument.java  0.4.0 / Oct 25, 2010
 *
 * Copyright (c) 2010 - 2011, cute4j.com and contributors.
 *
 * This software is licensed under the LGPL version 2.1 license.
 * A copy of this license is included in the file LICENSE-LGPL.txt.
 */
package com.cute4j.swing.text;

import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

import com.cute4j.core.AbstractDisposable;
import com.cute4j.core.DisposedError;
import com.cute4j.core.events.EventListenerList;
import com.cute4j.swing.Swing;
import com.cute4j.swing.events.UndoRedoListener;
import com.cute4j.swing.print.Printer;


/**
 * Abstract text document.
 *
 * @cute4j.native
 *    generate="header"
 *
 * @author Slobodan Vrkacevic
 * @version 0.4.0
 */
public abstract class TextDocument extends AbstractDisposable {

// Static Initialization. ------------------------------------------------------

  static {
    Swing.initialize();
    _initialize();
  }

// Package-Private Fields. -----------------------------------------------------

  long _delegate;
  boolean undoRedoEnabled;
  TextHighlighter highlighter;

// Protected Fields. -----------------------------------------------------------

  protected final EventListenerList listenerList;

// Package-Private Constructors. -----------------------------------------------

  TextDocument() {
    this(null);
  }

  TextDocument(String text) {
    listenerList = new EventListenerList();
    undoRedoEnabled = true;
    _delegate = _create(); // NOPMD
    if (_delegate == 0) {
      throw new OutOfMemoryError("Document creation failed");
    }
    if (text != null) {
      _setText(_delegate, text); // NOPMD
    }
  }

// Public Methods. -------------------------------------------------------------

  /**
   * Clears the text.
   */
  public void clearText() {
    disposedCheck();
    _clearText(_delegate);
  }

  /**
   * Returns text of the document.
   *
   * @return text of the document
   */
  public String getText() {
    disposedCheck();
    return _getText(_delegate);
  }

  /**
   * Sets text of the document.
   *
   * @param text the text of the document
   */
  public void setText(String text) {
    disposedCheck();
    _setText(_delegate, text);
  }

  /**
   * Appends text to the end of the document.
   *
   * @param text the text to be appended
   */
  public void appendText(String text) {
    disposedCheck();
    if (text != null) {
      _appendText(_delegate, text);
    }
  }

  /**
   * Returns <tt>true</tt> if the undo operation is available.
   *
   * @return <tt>true</tt> if the undo operation is available
   */
  public boolean isUndoAvailable() {
    if (undoRedoEnabled) {
      disposedCheck();
      return _isUndoAvailable(_delegate);
    }
    return false;
  }

  /**
   * Returns <tt>true</tt> if the redo operation is available.
   *
   * @return <tt>true</tt> if the redo operation is available
   */
  public boolean isRedoAvailable() {
    if (undoRedoEnabled) {
      disposedCheck();
      return _isRedoAvailable(_delegate);
    }
    return false;
  }

  /**
   * Sets whether or not the undo stack is enabled.
   *
   * @param enabled <tt>true</tt> if enabled
   */
  public void setUndoRedoEnabled(boolean enabled) {
    if (undoRedoEnabled != enabled) {
      disposedCheck();
      undoRedoEnabled = enabled;
      _setUndoRedoEnabled(_delegate, enabled);
    }
  }

  /**
   * Undos last action.
   */
  public void undo() {
    if (undoRedoEnabled) {
      disposedCheck();
      _undo(_delegate);
    }
  }

  /**
   * Redos last action.
   */
  public void redo() {
    if (undoRedoEnabled) {
      disposedCheck();
      _redo(_delegate);
    }
  }

  /**
   * Returns <tt>true</tt> if the document is modified.
   *
   * @return <tt>true</tt> if the document is modified
   */
  public boolean isModified() {
    disposedCheck();
    return _isModified(_delegate);
  }

  /**
   * Sets whether or not the document is modified.
   *
   * @param modified <tt>true</tt> if modified
   */
  public void setModified(boolean modified) {
    disposedCheck();
    _setModified(_delegate, modified);
  }

  /**
   * Reads text from reader.
   *
   * @param reader the reader
   *
   * @throws IOException on an I/O error
   */
  public void read(Reader reader) throws IOException {
    disposedCheck();
    // TODO: optimize ?
    StringBuffer sb = new StringBuffer();
    int chunk = 16 * 1024;
    char[] buffer = new char[chunk];
    while ((chunk = reader.read(buffer, 0, chunk)) != -1) { // NOPMD
      sb.append(buffer, 0, chunk);
    }
    setText(sb.toString());
  }

  /**
   * Reads text from reader without throwing exceptions.
   *
   * @param reader the reader
   *
   * @return <tt>true</tt> if succeed
   */
  public boolean readFrom(Reader reader) {
    if (reader == null) {
      throw new IllegalArgumentException("Null reader");
    }
    try {
      read(reader);
      return true;
    } catch (IOException ioe) {
      return false;
    } finally {
      try {
        reader.close();
      } catch (IOException ioe) { // NOPMD
        // empty catch block
      }
    }
  }

  /**
   * Reads text from the specified file without throwing exceptions.
   *
   * @param fileName the file name
   *
   * @return <tt>true</tt> if succeed
   */
  public boolean readFrom(String fileName) {
    if (fileName == null) {
      throw new IllegalArgumentException("Null fileName");
    }
    FileReader reader = null;
    try {
      reader = new FileReader(fileName);
      read(reader);
      return true;
    } catch (IOException ioe) {
      return false;
    } finally {
      try {
        if (reader != null) {
          reader.close();
        }
      } catch (IOException ioe) { // NOPMD
        // empty catch block
      }
    }
  }

  /**
   * Writes text to writer.
   *
   * @param writer the writer
   *
   * @throws IOException on an I/O error
   */
  public void write(Writer writer) throws IOException {
    String text = getText();
    if (text != null) {
      writer.write(text);
    }
    _setModified(_delegate, false);
  }

  /**
   * Prints the document on the specified printer.
   *
   * @param printer the printer
   */
  public void print(Printer printer) {
    if (printer == null) {
      throw new IllegalArgumentException("Null printer");
    }
    disposedCheck();
    if (printer.isDisposed()) {
      throw new DisposedError("Printer disposed");
    }
    _print(_delegate, printer);
  }

  /**
   * Returns highlighter.
   *
   * @return highlighter
   */
  public TextHighlighter getHighlighter() {
    return highlighter;
  }

  /**
   * Sets highlighter.
   *
   * @param highlighter the highlighter
   */
  public void setHighlighter(TextHighlighter highlighter) {
    if (this.highlighter != highlighter) {
      disposedCheck();
      this.highlighter = highlighter;
      _setUseHighlighter(_delegate, highlighter != null);
    }
  }

  /**
   * Adds undo/redo listener.
   *
   * @param listener the undo/redo listener
   */
  public void addUndoRedoListener(UndoRedoListener listener) {
    if (listener != null) {
      listenerList.add(UndoRedoListener.class, listener);
      setUndoRedoListening(
                   listenerList.getListenerCount(UndoRedoListener.class) == 1);
    }
  }

  /**
   * Remove undo/redo listener.
   *
   * @param listener the undo/redo listener
   */
  public void removeUndoRedoListener(UndoRedoListener listener) {
    if (listener != null) {
      listenerList.remove(UndoRedoListener.class, listener);
      setUndoRedoListening(
                   listenerList.getListenerCount(UndoRedoListener.class) == 0);
    }
  }

  /**
   * Returns all undo/redo listeners.
   *
   * @return the undo/redo listener array
   */
  public UndoRedoListener[] getUndoRedoListeners() {
    int count = listenerList.getListenerCount(UndoRedoListener.class);
    UndoRedoListener[] listeners = new UndoRedoListener[count];
    if (count > 0) {
      System.arraycopy(listenerList.getListenersArray(UndoRedoListener.class),
                       0, listeners,  0, count);
    }
    return listeners;
  }

// Protected Methods. ----------------------------------------------------------

  /**
   * Notifies listeners that undo operation is available.
   *
   * @param available <tt>true</tt> if available
   */
  protected void fireUndoAvailable(boolean available) {
    int count = listenerList.getListenerCount(UndoRedoListener.class);
    if (count > 0) {
      UndoRedoListener[] listeners =
          (UndoRedoListener[])listenerList.
                                      getListenersArray(UndoRedoListener.class);
      for (int i = count; --i >= 0;) {
        listeners[i].undoAvailable(available);
      }
    }
  }

  /**
   * Notifies listeners that redo operation is available.
   *
   * @param available <tt>true</tt> if available
   */
  protected void fireRedoAvailable(boolean available) {
    int count = listenerList.getListenerCount(UndoRedoListener.class);
    if (count > 0) {
      UndoRedoListener[] listeners =
          (UndoRedoListener[])listenerList.
                                      getListenersArray(UndoRedoListener.class);
      for (int i = count; --i >= 0;) {
        listeners[i].redoAvailable(available);
      }
    }
  }

// Overridden Methods. ---------------------------------------------------------

  /**
   * Returns object disposed error message.
   *
   * @return object disposed error message
   */
  protected String disposedErrorString() {
    return "Document disposed: " + toString();
  }

  /**
   * {@inheritDoc}
   */
  protected final void disposeObject() {
    if (_delegate != 0) {
      _destroy(_delegate);
      _delegate = 0;
    }
  }

// Package-Private Methods. ----------------------------------------------------

  void setUndoRedoListening(boolean listening) {
    if (_delegate != 0) {
      _setUndoRedoListening(_delegate, listening);
    }
  }

  void setFormat(int start, int length, CharacterFormat format) {
    if (_delegate != 0) {
      _setFormat(_delegate, start, length, format);
    }
  }

// Native Callbacks. -----------------------------------------------------------

  void _undoAvailable(boolean available)  {
    fireUndoAvailable(available);
  }

  void _redoAvailable(boolean available)  {
    fireRedoAvailable(available);
  }

  void _highlightBlock(int blockStart, String blockText) {
    highlighter.highlightDocumentBlock(this, blockStart, blockText);
  }

// Package-Private "Abstract Native" Methods. ----------------------------------

  abstract long _create();

// Package-Private Native Methods. ---------------------------------------------

  native void _clearText(long delegate);

  native String _getText(long delegate);

  native void _setText(long delegate, String text);

  native void _appendText(long delegate, String text);

  native boolean _isUndoAvailable(long delegate);

  native boolean _isRedoAvailable(long delegate);

  native void _undo(long delegate);

  native void _redo(long delegate);

  native void _setUndoRedoEnabled(long delegate, boolean enabled);

  native void _setUndoRedoListening(long delegate, boolean listening);

  native boolean _isModified(long delegate);

  native void _setModified(long delegate, boolean modified);

  native void _print(long delegate, Printer printer);

  native void _setUseHighlighter(long delegate, boolean useHighlighter);

  native void _setFormat(long delegate, int begin, int end,
                         CharacterFormat format);

  native void _destroy(long delegate);

// Private Static Native Methods. ----------------------------------------------

  private static native void _initialize();

}
